package org.zoomdev.zoom.protobuf.modules;

import io.protostuff.ByteArrayInput;
import io.protostuff.ProtostuffOutput;
import org.zoomdev.zoom.common.caster.Caster.CasterException;
import org.zoomdev.zoom.common.exceptions.CasException;
import org.zoomdev.zoom.common.exceptions.ServiceException;
import org.zoomdev.zoom.common.exceptions.SystemException;
import org.zoomdev.zoom.common.exceptions.ZoomException;
import org.zoomdev.zoom.common.utils.Classes;
import org.zoomdev.zoom.dao.DaoException;
import org.zoomdev.zoom.validator.ValidateException;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Type;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeoutException;

public final class IUnknownSchemaRegistry {
    private static final ConcurrentHashMap<Type, ISchema> map = new ConcurrentHashMap<>();
    private static final ArrayList<ISchema> schemas = new ArrayList<>();

    private static final StackTraceElement[] EMPTY = new StackTraceElement[]{};

    static {
        // 核心JDK异常
        register(IOException.class, new ExceptionSchema(IOException.class));
        register(IllegalArgumentException.class, new ExceptionSchema(IllegalArgumentException.class));
        register(TimeoutException.class, new ExceptionSchema(TimeoutException.class));
        register(NullPointerException.class, new ExceptionSchema(NullPointerException.class));
        register(SQLException.class, new ExceptionSchema(SQLException.class));
        register(IllegalStateException.class, new ExceptionSchema(IllegalStateException.class));
        register(IndexOutOfBoundsException.class, new ExceptionSchema(IndexOutOfBoundsException.class));
        register(FileNotFoundException.class, new ExceptionSchema(FileNotFoundException.class));
        register(InterruptedException.class, new ExceptionSchema(InterruptedException.class));

// 业务通用异常
        register(ServiceException.class, new ExceptionSchema(ServiceException.class));
        register(ValidateException.class, new ExceptionSchema(ValidateException.class));
        register(CasException.class, new ExceptionSchema(CasException.class));
//        register(NotFoundException.class, new ExceptionSchema(NotFoundException.class));
//        register(AuthException.class, new ExceptionSchema(AuthException.class));

// 领域特定异常
        register(SystemException.class, new ExceptionSchema(SystemException.class));
        register(CasterException.class, new ExceptionSchema(CasterException.class));
        register(ZoomException.class, new ExceptionSchema(ZoomException.class));
        register(DaoException.class, new ExceptionSchema(DaoException.class));

// 分布式系统异常
//        register(RpcException.class, new ExceptionSchema(RpcException.class));
//        register(CircuitBreakerException.class, new ExceptionSchema(CircuitBreakerException.class));
//        register(RateLimitExceededException.class, new ExceptionSchema(RateLimitExceededException.class));


        register(Set.class, new UnknownCollectionSchema(Set.class));
        register(Collection.class, new UnknownCollectionSchema(Collection.class));
        register(List.class, new UnknownCollectionSchema(List.class));


    //占位符
        register(Exception1.class, new ExceptionSchema(Exception1.class));
        register(Exception2.class, new ExceptionSchema(Exception2.class));
        register(Exception3.class, new ExceptionSchema(Exception3.class));
        register(Exception4.class, new ExceptionSchema(Exception4.class));
        register(Exception5.class, new ExceptionSchema(Exception5.class));
        register(Exception6.class, new ExceptionSchema(Exception6.class));
        register(Exception7.class, new ExceptionSchema(Exception7.class));
        register(Exception8.class, new ExceptionSchema(Exception8.class));
        register(Exception9.class, new ExceptionSchema(Exception9.class));
        register(Exception10.class, new ExceptionSchema(Exception10.class));
    }


    static class Exception1 extends Exception{

    }

    static class Exception2 extends Exception{

    }

    static class Exception3 extends Exception{

    }

    static class Exception4 extends Exception{

    }

    static class Exception5 extends Exception{

    }

    static class Exception6 extends Exception{

    }

    static class Exception7 extends Exception{

    }

    static class Exception9 extends Exception{

    }

    static class Exception8 extends Exception{

    }

    static class Exception10 extends Exception{

    }

    static ISchema getSchema(Class type) {
        ISchema schema = map.get(type);
        if(schema==null){
            throw new ZoomException("不支持的类型"+type);
        }
        return schema;
    }

    public static synchronized ISchema register(Class<?> type, ISchema schema) {
        return map.computeIfAbsent(type, k -> {
            WrapSchema sm = new WrapSchema(schema, schemas.size());
            schemas.add(sm);
            return sm;
        });
    }

    static ISchema getSchema(int index) {
        if (index < 0 || index >= schemas.size()) {
            throw new ZoomException("没有注册的schema index:" + index);
        }
        ISchema schema = schemas.get(index);
        if (schema == null) {
            throw new ZoomException("未注册的schema:" + index);
        }
        return schema;
    }

    public static class ExceptionSchema implements ISchema {

        private final Constructor c0;
        private final Constructor c1;
        private final Constructor c2;

        public ExceptionSchema(Class type) {
            Constructor c21;
            Constructor c01;
            Constructor c11;
            try {
                c01 = type.getConstructor();

            } catch (NoSuchMethodException e) {
                c01 = null;
            }

            this.c0 = c01;
            try {
                c11 = type.getConstructor(String.class);
            } catch (NoSuchMethodException e) {
                c11 = null;
            }

            this.c1 = c11;
            try {
                c21 = type.getConstructor(String.class, Throwable.class);
            } catch (NoSuchMethodException e) {
                c21 = null;
            }

            this.c2 = c21;
        }

        @Override
        public void write(ProtostuffOutput output, int index, Object value, boolean repeat) throws IOException {
            Throwable e = (Throwable) value;
            String message = e.getMessage();
            if(message!=null){
                output.writeString(1, message, false);
            }

            Throwable t = e.getCause();
            if (t != null) {
                if (index <= 4) {
                    ISchema schema = IUnknownSchemaRegistry.getSchema(t.getClass());
                    schema.write(output, index + 1, t, false);
                }
            }

        }

        @Override
        public Object mergeFrom(ByteArrayInput input) throws IOException {
            String message = null;
            Throwable cause = null;
            int field = input.readFieldNumber(null);
            if(field==1){
                message = input.readString();
            }
            field = input.readFieldNumber(null);
            if (field > 0) {
                int index = input.readInt32();
                ISchema schema = IUnknownSchemaRegistry.getSchema(index);
                cause = (Throwable) schema.mergeFrom(input);
            }
            Throwable throwable;
            if (message == null && cause == null) {
                throwable = (Throwable) Classes.newInstance(c0);
            } else if (cause == null) {
                throwable = (Throwable) Classes.newInstance(c1, message);
            } else {
                throwable = (Throwable) Classes.newInstance(c2, message, cause);
            }

            throwable.setStackTrace(EMPTY);
            return throwable;
        }
    }

    private static class WrapSchema implements ISchema {

        private final ISchema schema;
        private final int index;

        private WrapSchema(ISchema schema, int index) {
            this.schema = schema;
            this.index = index;
        }

        @Override
        public void write(ProtostuffOutput output, int index, Object value, boolean repeat) throws IOException {
            output.writeInt32(index, this.index, false);
            schema.write(output, index + 1, value, repeat);
        }

        @Override
        public Object mergeFrom(ByteArrayInput input) throws IOException {
            return schema.mergeFrom(input);
        }
    }
}
